/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License Version
 *  3 as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
 *  USA.
 */

#include "shell.h"
#include "ui_shell.h"
#include <QCompleter>
#include <QtScript>
#include <QtScriptTools>

#include <QScriptSyntaxCheckResult>
#include <QFile>
#include "highlighter.h"

#include "application.h"
#include "zonesmanager.h"
#include "alertwindow.h"
#include "optrzone.h"
#include "expandingwindow.h"
#include <Qsci/qsciscintilla.h>

O_Z

#define optra() OZ

        extern ZonesManager * zmanager;

namespace Optra{
    class GuiWindow;
};

using Optra::GuiWindow;

Q_DECLARE_METATYPE(GuiWindow*)

QScriptValue guiWindowToScriptValue(QScriptEngine *engine, GuiWindow* const &in)
{
    return engine->newQObject(in);
}

void guiWindowFromScriptValue(const QScriptValue &object, GuiWindow* &out)
{
    out = qobject_cast<Optra::GuiWindow*>(object.toQObject());
}


Q_DECLARE_METATYPE(AlertWindow*)

QScriptValue alertWindowToScriptValue(QScriptEngine *engine, AlertWindow* const &in)
{
    return engine->newQObject(in);
}

void alertWindowFromScriptValue(const QScriptValue &object, AlertWindow* &out)
{
    out = qobject_cast<AlertWindow*>(object.toQObject());
}


Q_DECLARE_METATYPE(ExpandingWindow*)

QScriptValue expandingWindowToScriptValue(QScriptEngine *engine, ExpandingWindow* const &in)
{
    return engine->newQObject(in);
}

void expandingWindowFromScriptValue(const QScriptValue &object, ExpandingWindow* &out)
{
    out = qobject_cast<ExpandingWindow*>(object.toQObject());
}


QScriptValue OptraPrint(QScriptContext *context, QScriptEngine *engine)
{
    QString result;
    for (int i = 0; i < context->argumentCount(); ++i) {
        if (i > 0)
            result.append(" ");
        result.append(context->argument(i).toString());
    }

    QScriptValue calleeData = context->callee().data();
    QTextEdit *edit = qobject_cast<QTextEdit*>(calleeData.toQObject());
    edit->append(result);

    return engine->undefinedValue();
}


Shell::Shell(QWidget *parent) :
        GuiWindow(parent), ui(new Ui::shell), completer(0), debugWindow(0)
{
    ui->setupUi(this);



    QFont font;
    font.setFamily("Courier");
    font.setFixedPitch(true);
    font.setPointSize(10);
    ui->command->setFont(font);
    highlighter = new Highlighter(ui->command->document());



    completer = new QCompleter(this);
    completer->setModel(modelFromFile(":/resources/wordlist.txt"));
    completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
    completer->setCaseSensitivity(Qt::CaseInsensitive);
    completer->setWrapAround(false);
    //ui->command->setCompleter(completer);

    connect(ui->start, SIGNAL(clicked()), SLOT(run()));
    connect(ui->debug, SIGNAL(clicked()), SLOT(debug()));


    qRegisterMetaType<GuiWindow*>("GuiWindow");


    engine = new QScriptEngine;
    connect(engine, SIGNAL(signalHandlerException()), SLOT(reportScriptError()));

    //QObject::connect(m_env, SIGNAL(scriptError(QScriptValue)),  this, SLOT(reportScriptError(QScriptValue)));
    qScriptRegisterMetaType(engine, guiWindowToScriptValue, guiWindowFromScriptValue);
    qScriptRegisterMetaType(engine, alertWindowToScriptValue, alertWindowFromScriptValue);
    qScriptRegisterMetaType(engine, expandingWindowToScriptValue, expandingWindowFromScriptValue);

    debugger = new QScriptEngineDebugger(this);

    QScriptValue objectValue = engine->newQObject(this);
    engine->globalObject().setProperty("main", objectValue);

    QScriptValue fun = engine->newFunction(OptraPrint);
    fun.setData(engine->newQObject(ui->result));
    engine->globalObject().setProperty("print", fun);


    QFile file("code.oscript");
    file.open(QIODevice::Text | QIODevice::ReadOnly);
    if (file.isOpen()){
        ui->command->setText(file.readAll());
    }

    //ByteArrayClass *byteArrayClass = new ByteArrayClass(eng);
    //globalObject.setProperty("ByteArray", byteArrayClass->constructor());

}

Shell::~Shell()
{
    delete ui;
    delete engine;


}

void Shell::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

QAbstractItemModel *Shell::modelFromFile(const QString& fileName)
{
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly))
        return new QStringListModel(completer);

#ifndef QT_NO_CURSOR
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
#endif
    QStringList words;

    while (!file.atEnd()) {
        QByteArray line = file.readLine();
        if (!line.isEmpty())
            words << line.trimmed();
    }

#ifndef QT_NO_CURSOR
    QApplication::restoreOverrideCursor();
#endif

    return new QStringListModel(words, completer);
}


void Shell::reportScriptError(const QScriptValue &error)
{
    static QString html = tr("<div style=\"color:red;\"><b>Line %0:</b>%1</div>");
    ui->result->setHtml(html.arg(error.property("lineNumber").toInt32()).arg(error.toString()));
}

void Shell::execute(bool debug)
{

    QString text = ui->command->toPlainText();

    QScriptSyntaxCheckResult syntax = QScriptEngine::checkSyntax(text);
    if (syntax.state() == QScriptSyntaxCheckResult::Error){
        QString html = "<div><b>Line%1</b><i>Message:%2</i></div>";
        ui->result->setHtml(html.arg(syntax.errorLineNumber()).arg(syntax.errorMessage()));
        return;
    }

    engine->clearExceptions();
    QTime operationTime;
    operationTime.start();
    ui->result->clear();

    if (debug){
       debugWindow = debugger->standardWindow();
       debugWindow->setWindowModality(Qt::ApplicationModal);
       debugWindow->resize(1280, 704);
       debugger->attachTo(engine);
       debugger->action(QScriptEngineDebugger::InterruptAction)->trigger();
    }else{
        debugger->detach();
    }


    QScriptValue globalObject = engine->globalObject();

    if (!globalObject.property("optra").isObject()){
        QScriptValue mainZoneObject = engine->newQObject(optra());
        engine->globalObject().setProperty("optra", mainZoneObject);
    }


    ZonesManager::ZoneProcessMap m = zmanager->zoneProcess();
    foreach(ZoneProcess p, m){
        if (!globalObject.property(p.info->id()).isObject()){
            QScriptValue zo = engine->newQObject(p.zinterface);
            engine->globalObject().setProperty(p.info->id(), zo);
        }

    }


    QScriptValue result = engine->evaluate(text);

    optra()->debug(result.toString());

    if (debugWindow){
        debugWindow->hide();
    }

    if (result.isError()){
        reportScriptError(result);
    }

    ui->result->append(QString("Time elapsed: %1 ms").arg(operationTime.elapsed()));

    QFile file("code.oscript");
    file.open(QIODevice::Truncate | QIODevice::Text | QIODevice::WriteOnly);
    if (file.isOpen()){
        QTextStream out(&file);
        out << ui->command->toPlainText();
        file.close();
    }
}


void Shell::run()
{
    execute(false);
}

void Shell::debug()
{
    execute(true);
}
